/*
 *  Copyright 2010 Wesley Gooch, Ryan Nix, Cody Wenzel.
 *  Montana State University - Spring Semester 2010
 *  Computer Science 450 - Compilers
 *  Project: Micro-Pascal Compiler
 */
package msu.compiler.parser;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import msu.compiler.codegenerators.CodeGen;
import msu.compiler.errorhandling.CompileException;
import msu.compiler.errorhandling.ErrorHandler;
import msu.compiler.scanner.Scanner;
import msu.compiler.scanner.Token;
import msu.compiler.semantics.AbstractMethodSymbol;
import msu.compiler.semantics.SemanticAnalyzer;
import msu.compiler.semantics.SemanticRecord;
import msu.compiler.semantics.Symbol;

/**
 *
 * @author Wesley Gooch
 */
public class Parser {

    private Scanner scanner;
    private SemanticAnalyzer analyzer;
    private Token currentToken;
    BufferedWriter logger;
    boolean screenlog = false; // set to true when debugging
    private CodeGen codeGen;

    //global info to declare whether a pushed var is by reference or not, along with its position
    private String methodName;
    private Stack<String> methodNameStack;
    private int parameterNumber;
    private Stack<Integer> paramNumStack;

    public Parser(Scanner scanner) {
        this.scanner = scanner;
        analyzer = new SemanticAnalyzer();
        codeGen  = new CodeGen(analyzer);
        parameterNumber = 0;
        methodName = null;
        methodNameStack = new Stack<String>();
        paramNumStack = new Stack<Integer>();
    }

    public void parse() {
        try {
            FileWriter fstream = new FileWriter("parse-tree.txt");
            logger = new BufferedWriter(fstream);
            codeGen.buildFile();
        } catch (Exception e) {
            System.err.println("logger error");
        }
        scanNextToken();
        systemGoal();
        if(ErrorHandler.doErrorsExist()) {
            System.out.println("Parse Completed but with errors. Code Generation was terminated.\n");
        } else {
            System.out.println("Parse Completed with no errors! \n");
        }
        analyzer.printSymbolTable();
        try {
            logger.close();
            codeGen.close();
        } catch (Exception e) {
            System.err.println("logger error");
        }
    }

    // For debugging purposes.
    private void printToken(int count) {
        System.out.print("  " + Integer.toString(count) + ") "
                + currentToken.toString() + " Type - "
                + Token.getTypeString(currentToken.getType()) + "\n");
    }

    private Token getToken() {
        return currentToken;
    }

    private void scanNextToken() {
        currentToken = scanner.scanNextToken();
    }

    private void reportError(String errorMessage) {
        String newMessage = "Error: " + errorMessage
                + currentToken.positionToString();
        ErrorHandler.logError(newMessage);
        try {
            logger.write(newMessage);
            logger.newLine();
            logger.flush();
        } catch (Exception e) {
            System.out.println("Logger error");
        }
    }

    private void match(byte type) {
        if (!(currentToken.getType() == type)) {
            reportError("Token " + Token.getTypeString(type) + " expected but "
                    + Token.getTypeString(currentToken.getType()) + " given.");
            try {
                logger.write("Match Error");
                logger.newLine();
                logger.flush();
            } catch (Exception e) {
                System.out.println("Logger error");
            }
        } else {
            try {
                logger.write("Matched " + Token.getTypeString(currentToken.getType()));
                logger.newLine();
                logger.flush();
            } catch (Exception e) {
                System.out.println("Logger error");
            }
        }

        scanNextToken();
    }

    private void log(int rulenum) {
        String message = "Applied rule " + rulenum;
        try {
            if (screenlog) {
                System.out.println(message);
            }
            logger.write(message);
            logger.newLine();
            logger.flush();
        } catch (Exception e) {
            System.out.println("logger error");
        }
    }

    //----------------------------------------------------------------
    // Start of Rules
    //----------------------------------------------------------------
    private void systemGoal() {
        //SystemGoal      --> Program $
        if (currentToken.getType() == Token.MP_PROGRAM) {
            log(1);
            program();
            match(Token.MP_EOF);
        } else {
            reportError(currentToken.getLexeme() + " cannot start a system goal.");
        }
    }

    private void program() {
        //Program         --> ProgramHeading ";" Block "."
        if (currentToken.getType() == Token.MP_PROGRAM) {
            log(2);
            programHeading();
            codeGen.setupProg(); //hack: level 0 is base
            match(Token.MP_SCOLON);
            block();
            match(Token.MP_PERIOD);
            codeGen.finish();
        } else {
            reportError(currentToken.getLexeme() + " cannot start a program.");
        }
    }

    private void programHeading() {
        //ProgramHeading  --> "program" ProgramIdentifier
        if (currentToken.getType() == Token.MP_PROGRAM) {
            SemanticRecord record = new SemanticRecord();
            log(3);
            match(Token.MP_PROGRAM);
            try {
                analyzer.createScope(programIdentifier());
            } catch (Exception e) {
                e.getMessage();
            }
        } else {
            reportError(currentToken.getLexeme() + " cannot start a program.");
        }
    }

    private void block() {
        //Block           --> VariableDeclarationPart ProcedureAndFunctionDeclarationPart StatementPart
        log(4);
        //should have type passed into it?
        //#create_new_table
        //#push onto stack
        switch (currentToken.getType()) {
            case Token.MP_VAR:
            case Token.MP_BEGIN:
            case Token.MP_FUNCTION:
            case Token.MP_PROCEDURE:
                variableDeclarationPart();
                codeGen.start();
                //add codeGen branch past function/proc declaration
                codeGen.branchToBegin();
                procedureAndFunctionDeclarationPart();
                //add codeGen label to branch over ^
                codeGen.beginLabel();
                statementPart();
                codeGen.end();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a block.");
        }
    }

    private void variableDeclarationPart() {
        /*
        VariableDeclarationPart  --> "var" VariableDeclaration ";" VariableDeclarationTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_VAR) {
            log(5);
            match(Token.MP_VAR);
            variableDeclaration();
            match(Token.MP_SCOLON);
            variableDeclarationTail();
        } else if (currentToken.getType() == Token.MP_BEGIN
                || currentToken.getType() == Token.MP_FUNCTION
                || currentToken.getType() == Token.MP_PROCEDURE) {
            //lambda
            log(6);
        } else {
            reportError(currentToken.getLexeme() + " cannot start a variable declaration.");
        }
    }

    private void variableDeclarationTail() {
        /*
        VariableDeclarationTail  --> VariableDeclaration ";" VariableDeclarationTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(7);
            variableDeclaration();
            match(Token.MP_SCOLON);
            variableDeclarationTail();
        } else if (currentToken.getType() == Token.MP_BEGIN
                || currentToken.getType() == Token.MP_FUNCTION
                || currentToken.getType() == Token.MP_PROCEDURE) {
            //lambda
            log(8);
        } else {
            reportError(currentToken.getLexeme() + " cannot start a variable declaration tail.");
        }
    }

    private void variableDeclaration() {
        //VariableDeclaration      --> Identifierlist ":" Type
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(9);
            List<SemanticRecord> idList_rec = new ArrayList();
            List<Token> id_list = identifierList();
            for (int i = 0; i < id_list.size(); i++) {
                idList_rec.add(new SemanticRecord());
                idList_rec.get(i).setToken(id_list.get(i));
                idList_rec.get(i).setKind(SemanticRecord.VARIABLE_SYMBOL);
            }
            match(Token.MP_COLON);
            type(idList_rec);
            try {
                for (int i = 0; i < id_list.size(); i++) {
                    analyzer.insertSymbol(idList_rec.get(i).createSymbol());
                }
            } catch (Exception ex) {
                ex.getMessage();
            }
        } else {
            reportError(currentToken.getLexeme() + " cannot start an identifier list.");
        }
    }

    private void type(List<SemanticRecord> record) {
        /*
        Type                     --> "Integer"
        --> "Float"
         */

        //after type match, search through local table and set all type=null to the matched value
        switch (currentToken.getType()) {
            case Token.MP_INTEGER:
                log(10);
                for (int i = 0; i < record.size(); i++) {
                    record.get(i).setType(Symbol.TYPE_INT);
                }
                match(Token.MP_INTEGER);
                break;
            case Token.MP_FLOAT:
                log(11);
                for (int i = 0; i < record.size(); i++) {
                    record.get(i).setType(Symbol.TYPE_FLOAT);
                }
                match(Token.MP_FLOAT);
                break;
            default:
                reportError(currentToken.getLexeme() + " must be Integer or Float.");
                break;
        }
    }

    private void type(SemanticRecord record) {
        /*
        Type                     --> "Integer"
        --> "Float"
         */

        //after type match, search through local table and set all type=null to the matched value
        switch (currentToken.getType()) {
            case Token.MP_INTEGER:
                log(10);
                record.setType(Symbol.TYPE_INT);
                match(Token.MP_INTEGER);
                break;
            case Token.MP_FLOAT:
                log(11);
                record.setType(Symbol.TYPE_FLOAT);
                match(Token.MP_FLOAT);
                break;
            default:
                reportError(currentToken.getLexeme() + " must be Integer or Float.");
                break;
        }
    }

    private void procedureAndFunctionDeclarationPart() {
        /*
        ProcedureAndFunctionDeclarationPart --> ProcedureDeclaration ProcedureAndFunctionDeclarationPart
        --> FunctionDeclaration ProcedureAndFunctionDeclarationPart
        --> ?
         */
        switch (currentToken.getType()) {
            case Token.MP_PROCEDURE:
                log(12);
                procedureDeclaration();
                procedureAndFunctionDeclarationPart();
                break;
            case Token.MP_FUNCTION:
                log(13);
                functionDeclaration();
                procedureAndFunctionDeclarationPart();
                break;
            case Token.MP_BEGIN:
                //lambda
                log(14);
                break;
            default:
                reportError("Illegal start of procedure or function declaration.");
        }
    }

    private void procedureDeclaration() {
        //ProcedureDeclaration                --> ProcedureHeading ";" Block ";"
        if (currentToken.getType() == Token.MP_PROCEDURE) {
            log(15);
            SemanticRecord record = new SemanticRecord();
            record.setKind(SemanticRecord.PROCEDURE_SYMBOL);
            procedureHeading(record);
            match(Token.MP_SCOLON);
            try {
                analyzer.insertSymbol(record.createSymbol());
                analyzer.createScope(record.getSymbol().getLexeme(), record.getSymbol().getLabel());
                for(int i = 0; i < record.getParameters().size(); i++) {
                    SemanticRecord parameter = (SemanticRecord) record.getParameters().get(i);
                    analyzer.insertSymbol(parameter.createSymbol());
                }
                analyzer.addReturnSpacer();
            } catch (Exception e) {
                e.getMessage();
            }
            block();
            match(Token.MP_SCOLON);
            analyzer.destroyScope();
        } else {
            reportError(currentToken.getLexeme() + " cannot start a procedure declaration");
        }
    }

    private void functionDeclaration() {
        //FunctionDeclaration                 --> FunctionHeading ";" Block ";"
        if (currentToken.getType() == Token.MP_FUNCTION) {
            log(16);
            SemanticRecord record = new SemanticRecord();
            record.setKind(SemanticRecord.FUNCTION_SYMBOL);
            functionHeading(record);
            match(Token.MP_SCOLON);
            try {
                analyzer.insertSymbol(record.createSymbol());
                analyzer.createScope(record.getSymbol().getLexeme(), record.getSymbol().getLabel());
                for(int i = 0; i < record.getParameters().size(); i++) {
                    SemanticRecord parameter = (SemanticRecord) record.getParameters().get(i);
                    analyzer.insertSymbol(parameter.createSymbol());
                }
                analyzer.addReturnSpacer();
            } catch (Exception e) {
                e.getMessage();
            }
            block();
            match(Token.MP_SCOLON);
            analyzer.destroyScope();
        } else {
            reportError(currentToken.getLexeme() + " cannot start a function Declaration");
        }
    }

    private void procedureHeading(SemanticRecord record) {
        //ProcedureHeading                    --> "procedure" procedureIdentifier OptionalFormalParameterList
        if (currentToken.getType() == Token.MP_PROCEDURE) {
            log(17);
            match(Token.MP_PROCEDURE);
            procedureIdentifier(record);
            optionalFormalParameterList(record);
            record.setType((byte) 99); //PROCEDURES DO NOT HAVE A RETURNTYPE
        } else {
            reportError(currentToken.getLexeme() + "Error in procedure heading");
        }
    }

    private void functionHeading(SemanticRecord record) {
        //FunctionHeading                     --> "function" functionIdentifier OptionalFormalParameterList Type
        if (currentToken.getType() == Token.MP_FUNCTION) {
            log(18);
            match(Token.MP_FUNCTION);
            functionIdentifier(record);
            optionalFormalParameterList(record);
            type(record);
        } else {
            reportError(currentToken.getLexeme() + "Error in function heading");
        }
    }

    private void optionalFormalParameterList(SemanticRecord record) {
        /*
        OptionalFormalParameterList         --> "(" FormalParameterSection FormalParameterSectionTail ")"
        --> ?
         */
        if (currentToken.getType() == Token.MP_LPAREN) {
            log(19);
            match(Token.MP_LPAREN);
            formalParameterSection(record);
            formalParameterSectionTail(record);
            match(Token.MP_RPAREN);
        } else if (currentToken.getType() == Token.MP_SCOLON
                || currentToken.getType() == Token.MP_FLOAT
                || currentToken.getType() == Token.MP_INTEGER) {
            //lambda
            log(20);
        } else {
            reportError("Illegal use of an Optional Formal Parameter List.");
        }

    }

    private void formalParameterSectionTail(SemanticRecord record) {
        /*
        FormalParameterSectionTail          --> ";" FormalParameterSection FormalParameterSectionTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_SCOLON) {
            match(Token.MP_SCOLON);
            formalParameterSection(record);
            formalParameterSectionTail(record);
            log(21);
        } else if (currentToken.getType() == Token.MP_RPAREN) {
            //lambda
            log(22);
        } else {
            reportError("Illegal ending of a Parameter Section Tail.");
        }

    }

    private void formalParameterSection(SemanticRecord record) {
        /*
        FormalParameterSection     --> ValueParameterSection
        --> VariableParameterSection
         */
        switch (currentToken.getType()) {
            case Token.MP_IDENTIFIER:
                log(23);
                valueParameterSection(record);
                break;
            case Token.MP_VAR:
                log(24);
                variableParameterSection(record);
                break;
            default:
                reportError("Illegal Formal Parameter Secion.");
        }
    }

    private void valueParameterSection(SemanticRecord record) {
        //ValueParameterSection            --> IdentifierList ":" Type
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(25);
            ArrayList<SemanticRecord> parameters = new ArrayList();
            List<Token> id_list = identifierList();
            for (int i = 0; i < id_list.size(); i++) {
                parameters.add(new SemanticRecord());
                parameters.get(i).setToken(id_list.get(i));
                parameters.get(i).setKind(SemanticRecord.PARAMETER_SYMBOL);
                parameters.get(i).setMode(SemanticRecord.MODE_VAL);
            }

            match(Token.MP_COLON);
            type(parameters);

            for (int i = 0; i < parameters.size(); i++)
                record.addParameter(parameters.get(i));

        } else {
            reportError("Illegal Value Parameter Secion.");
        }
    }

    private void variableParameterSection(SemanticRecord record) {
        //VariableParameterSection            --> "var" IdentifierList ":" Type
        if (currentToken.getType() == Token.MP_VAR) {
            log(26);
            match(Token.MP_VAR);
            ArrayList<SemanticRecord> parameters = new ArrayList();
            List<Token> id_list = identifierList();
            for (int i = 0; i < id_list.size(); i++) {
                parameters.add(new SemanticRecord());
                parameters.get(i).setToken(id_list.get(i));
                parameters.get(i).setKind(SemanticRecord.VARIABLE_SYMBOL);
                parameters.get(i).setMode(SemanticRecord.MODE_REF);
            }

            match(Token.MP_COLON);
            type(parameters);
            for(int i = 0; i < parameters.size(); i++)
                record.addParameter(parameters.get(i));
        } else {
            reportError("Illegal Variable Parameter Secion.");
        }
    }

    private void statementPart() {
        //StatementPart      --> CompoundStatement
        if (currentToken.getType() == Token.MP_BEGIN) {
            log(27);
            compoundStatement();
        } else {
            reportError("Illegal Statement Part.");
        }
    }

    private void compoundStatement() {
        //CompoundStatement  --> "begin" StatementSequence "end"
        if (currentToken.getType() == Token.MP_BEGIN) {
            log(28);
            codeGen.comment("begin "+analyzer.getCurrentScope().getName());
            match(Token.MP_BEGIN);
            statementSequence();
            match(Token.MP_END);
            codeGen.comment("end "+analyzer.getCurrentScope().getName());
        } else {
            reportError("Illegal Compound Statement.");
        }
    }

    private void statementSequence() {
        //StatementSequence  --> Statement StatementTail
        switch (currentToken.getType()) {
            case Token.MP_SCOLON:
            case Token.MP_BEGIN:
            case Token.MP_END:
            case Token.MP_FOR:
            case Token.MP_IDENTIFIER:
            case Token.MP_IF:
            case Token.MP_READ:
            case Token.MP_REPEAT:
            case Token.MP_UNTIL:
            case Token.MP_WHILE:
            case Token.MP_WRITE:
                log(29);
                statement();
                statementTail();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a statement sequence.");
        }
    }

    private void statementTail() {
        /*
        StatementTail      --> ";" Statement StatementTail
        --> ?
         */

        switch (currentToken.getType()) {
            case Token.MP_END:
            case Token.MP_UNTIL:
                //lambda
                log(31);
                break;
            case Token.MP_SCOLON:
                log(30);
                match(Token.MP_SCOLON);
                statement();
                statementTail();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot end a statement sequence.");
        }
    }

    private void statement() {
        /*
        Statement      --> EmptyStatement
        --> CompoundStatement
        --> ReadStatement
        --> WriteStatement
        --> AssignmentStatement
        --> IfStatement
        --> WhileStatement
        --> RepeatStatement
        --> ForStatement
        --> ProcedureStatement
         */
        switch (currentToken.getType()) {
            case Token.MP_READ:
                log(34);
                readStatement();
                break;
            case Token.MP_WRITE:
                log(35);
                writeStatement();
                break;
            case Token.MP_IDENTIFIER:
                //Hack for conflict resolution
                String s = currentToken.getLexeme();
                match(Token.MP_IDENTIFIER);
                if (currentToken.getType() == Token.MP_ASSIGN) {
                    log(36);
                    assignmentStatement(s);
                } else {
                    if (analyzer.lookupSymbol(s).getKind().equals("Function")) {
                        reportError("A function can not be used like a procedure");
                    }
                    log(41);
                    procedureStatement(s);
                }
                break;
            case Token.MP_BEGIN:
                log(33);
                compoundStatement();
                break;
            case Token.MP_IF:
                log(37);
                ifStatement();
                break;
            case Token.MP_WHILE:
                log(38);
                whileStatement();
                break;
            case Token.MP_REPEAT:
                log(39);
                repeatStatement();
                break;
            case Token.MP_FOR:
                log(40);
                forStatement();
                break;
            case Token.MP_SCOLON:
            case Token.MP_END:
            case Token.MP_ELSE:
            case Token.MP_UNTIL:
                //Lambda
                log(32);
                emptyStatement();
                break;
            default:
                reportError("Illegal statement syntax.");
                break;
        }
    }

    private void emptyStatement() {
        //EmptyStatement      --> ?
        //LAMBDA
        if (currentToken.getType() == Token.MP_SCOLON
                || currentToken.getType() == Token.MP_ELSE
                || currentToken.getType() == Token.MP_END
                || currentToken.getType() == Token.MP_UNTIL) {
            log(42);
            //lambda
        } else {
            reportError(currentToken.getLexeme() + " cannot be in an empty statement.");
        }
    }

    private void readStatement() {
        //ReadStatement       --> "read" "(" ReadParameter ReadParameterTail ")"
        if (currentToken.getType() == Token.MP_READ) {
            log(43);
            match(Token.MP_READ);
            codeGen.comment("read");
            match(Token.MP_LPAREN);
            readParameter();
            readParameterTail();
            match(Token.MP_RPAREN);
        } else {
            reportError("Illegal Read Statement.");
        }
    }

    private void readParameterTail() {
        /*
        ReadParameterTail   --> "," ReadParameter ReadParameterTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_COMMA) {
            log(44);
            match(Token.MP_COMMA);
            readParameter();
            readParameterTail();
        } else if (currentToken.getType() == Token.MP_RPAREN) {
            //LAMBDA
            log(45);
        } else {
            reportError(currentToken.getLexeme() + " cannot separate a parameter list.");
        }
    }

    private void readParameter() {
        //ReadParameter       --> VariableIdentifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(46);
            Token token = variableIdentifier();
            codeGen.readParam(token.getLexeme());
        } else {
            reportError("Illegal Read Parameter.");
        }
    }

    private void writeStatement() {
        //WriteStatement      --> "write" "(" WriteParameter WriteParameterTail ")"
        if (currentToken.getType() == Token.MP_WRITE) {
            log(47);
            match(Token.MP_WRITE);
            codeGen.comment("Write");
            match(Token.MP_LPAREN);
            writeParameter();
            writeParameterTail();
            match(Token.MP_RPAREN);
        } else {
            reportError("Illegal Write Statement.");
        }
    }

    private void writeParameterTail() {
        /*
        WriteParameterTail  --> "," WriteParameter WriteParameterTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_COMMA) {
            log(48);
            match(Token.MP_COMMA);
            writeParameter();
            writeParameterTail();
        } else if (currentToken.getType() == Token.MP_RPAREN) {
            //LAMBDA
            log(49);
        } else {
            reportError(currentToken.getLexeme() + " cannot be a Write Parameter Tail.");
        }
    }

    private void writeParameter() {
        //WriteParameter      --> OrdinalExpression
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_MINUS:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_LPAREN:
                log(50);
                ordinalExpression();
                codeGen.writeParam();
                break;
            default:
                reportError("Illegal Write Parameter syntax.");
        }

    }

    private void assignmentStatement(String s) {
        /*
        AssignmentStatement --> VariableIdentifier ":=" Expression
        --> FunctionIdentifier ":=" Expression
         */

        // Cody - We already matched Identifier in statement() to prevent conflict
        if (currentToken.getType() == Token.MP_ASSIGN){
            List <Symbol> currentSymbols = analyzer.getTableSymbols(analyzer.getCurrentScope().getNestLevel());
            if(currentSymbols.size() == 0 || currentSymbols.get(currentSymbols.size() - 1).getKind().equals("Variable"))
            {
               log(51); 
            }else
            {
               log(52); 
            }
        }
        else
        {
            reportError("Illegal syntax for Assignment Statement.");
        }
        
        match(Token.MP_ASSIGN);
        expression();
        codeGen.assign(s);
    }

    private void ifStatement() {
        //IfStatement         --> "if" BooleanExpression "then" Statement OptionalElsePart
        if (currentToken.getType() == Token.MP_IF) {
            log(53);
            match(Token.MP_IF);
            booleanExpression();
            
            codeGen.comment("If");
            codeGen.ifThenStatement();

            match(Token.MP_THEN);
            statement();

            codeGen.breakToEndIf();
            
            codeGen.comment("Else");
            codeGen.elseStatement();


            optionalElsePart();
            codeGen.endIf();

        } else {
            reportError("Illegal If Statement.");
        }
    }

    private void optionalElsePart() {
        /*
        OptionalElsePart    --> "else" Statement
        --> ?
         *
         *
         */

        switch (currentToken.getType()) {
            case Token.MP_UNTIL:
            case Token.MP_END:
            case Token.MP_SCOLON:
                //lambda
                log(55);
                break;
            //We assume that the 'else' statement is linked to the nearest 'if'
            case Token.MP_ELSE:
                log(54);
                match(Token.MP_ELSE);
                statement();
                break;
            default:
                reportError("Illegal else statement.");
        }
    }

    private void repeatStatement() {
        //RepeatStatement     --> "repeat" StatementSequence "until" BooleanExpression
        if (currentToken.getType() == Token.MP_REPEAT) {
            log(56);
            match(Token.MP_REPEAT);
            codeGen.comment("Repeat Until");
            codeGen.repeatStatement();
            statementSequence();
            match(Token.MP_UNTIL);
            booleanExpression();
            codeGen.endRepeat();
        } else {
            reportError("Illegal Repeat Statement.");
        }
    }

    private void whileStatement() {
        //WhileStatement      --> "while" BooleanExpression "do" Statement
        if (currentToken.getType() == Token.MP_WHILE) {
            log(57);
            match(Token.MP_WHILE);

            codeGen.comment("While");
            codeGen.startWhile();
            
            booleanExpression();

            codeGen.whileStatement();

            match(Token.MP_DO);
            statement();
            
            codeGen.endWhile();
        } else {
            reportError("Illegal While Statement.");
        }
    }

    private void forStatement() {
        //ForStatement        --> "for" ControlVariable ":=" InitialValue StepValue FinalValue "do" Statement
        if (currentToken.getType() == Token.MP_FOR) {
            log(58);
            match(Token.MP_FOR);
            codeGen.comment("For statement");
            //The control variable MUST be declared before use in loop
            String s = controlVariable();
            match(Token.MP_ASSIGN);
            initialValue();
            codeGen.comment("Initial Assignment");
            codeGen.assign(s); //assign value to the control variable
            byte op = stepValue();
            codeGen.startFor(s); //pushes the variable on the stack
            finalValue();       //pushes the final value onto the stack
            codeGen.compare(op); //compare by the step value, leaves 0/1 on the stack
            codeGen.forStatement(); //if 0 left onthe stack, break out.
            match(Token.MP_DO);
            statement();
            codeGen.endFor(s, op);
        } else {
            reportError("Illegal For Statement.");
        }
    }

    private String controlVariable() {
        //ControlVariable     --> VariableIdentifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(59);
            Token id = variableIdentifier();
            return id.getLexeme();
        } else {
            reportError("Illegal Control Variable.");
            return null;
        }
    }

    private void initialValue() {
        //InitialValue        --> OrdinalExpression

        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(60);
                ordinalExpression();
                break;
            default:
                reportError("Illegal Initial Value.");
        }
    }

    private Byte stepValue() {
        /*
        StepValue  --> "to"
        --> "downto"
         */
        switch (currentToken.getType()) {
            case Token.MP_TO:
                log(61);
                match(Token.MP_TO);
                return Token.MP_LEQUAL;
            case Token.MP_DOWNTO:
                log(62);
                match(Token.MP_DOWNTO);
                return Token.MP_GEQUAL;
            default:
                reportError(currentToken.getLexeme() + " is not a legal step value.");
                return -1;
        }
    }

    private void finalValue() {
        //FinalValue          --> OrdinalExpression
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(63);
                ordinalExpression();
                break;
            default:
                reportError("Illegal Final Value syntax.");
        }
    }

    private void procedureStatement(String s) { //s is the procedure Name
        //ProcedureStatement  --> ProcedureIdentifier OptionalActualParameterList

        //We have already matched Identifier to avoid conflicts above

        switch (currentToken.getType()) {
            case Token.MP_COMMA:
            case Token.MP_SCOLON:
            case Token.MP_RPAREN:
            case Token.MP_EQUAL:
            case Token.MP_GTHAN:
            case Token.MP_LTHAN:
            case Token.MP_GEQUAL:
            case Token.MP_LEQUAL:
            case Token.MP_NEQUAL:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
            case Token.MP_AND:
            case Token.MP_TIMES:
            case Token.MP_DIV:
            case Token.MP_DO:
            case Token.MP_DOWNTO:
            case Token.MP_ELSE:
            case Token.MP_END:
            case Token.MP_MOD:
            case Token.MP_OR:
            case Token.MP_THEN:
            case Token.MP_TO:
            case Token.MP_UNTIL:
            case Token.MP_LPAREN:
                //setup register
                log(64);
                methodName = s;
                codeGen.setup(s);
                methodNameStack.push(methodName);
                paramNumStack.push(parameterNumber);
                //optionalActualParameterList eventually calls expression(s)
                //expression(s) leave values on stack
                //By default, optionalactualparameterlist will push parameters on stack
                optionalActualParameterList();
                parameterNumber = paramNumStack.pop();
                methodName = methodNameStack.pop();
                methodName= null;
                parameterNumber = 0;
                //now move to procedure
                codeGen.call(s);
                codeGen.teardown(s);
                break;
            default:
                reportError("Illegal Procedure Statement.");
        }
    }

    private void optionalActualParameterList() {
        /*
        OptionalActualParameterList --> "(" ActualParameter ActualParameterTail ")"
        --> ?
         */
        switch (currentToken.getType()) {
            case Token.MP_COMMA:
            case Token.MP_SCOLON:
            case Token.MP_RPAREN:
            case Token.MP_EQUAL:
            case Token.MP_GTHAN:
            case Token.MP_LTHAN:
            case Token.MP_GEQUAL:
            case Token.MP_LEQUAL:
            case Token.MP_NEQUAL:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
            case Token.MP_AND:
            case Token.MP_TIMES:
            case Token.MP_DIV:
            case Token.MP_DO:
            case Token.MP_DOWNTO:
            case Token.MP_ELSE:
            case Token.MP_END:
            case Token.MP_MOD:
            case Token.MP_OR:
            case Token.MP_THEN:
            case Token.MP_TO:
            case Token.MP_UNTIL:
                //lambda
                log(66);
                break;
            case Token.MP_LPAREN:
                log(65);
                match(Token.MP_LPAREN);
                actualParameter();
                parameterNumber = paramNumStack.peek();
                methodName = methodNameStack.peek();
                actualParameterTail();
                match(Token.MP_RPAREN);
                break;
            default:
                reportError("Illegal Actual Parameter List syntax.");
        }
    }

    private void actualParameterTail() {
        /*
        ActualParameterTail --> "," ActualParameter ActualParameterTail
        --> ?
         */
        if (currentToken.getType() == Token.MP_COMMA) {
            log(67);
            match(Token.MP_COMMA);
            actualParameter();
            actualParameterTail();
        } else if (currentToken.getType() == Token.MP_RPAREN) {
            //lambda
            log(68);
        } else {
            reportError(currentToken.getLexeme() + " cannot be a Parameter List Tail.");
        }
    }

    private void actualParameter() {
        //ActualParameter     --> OrdinalExpression
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(69);
                ordinalExpression();
                parameterNumber++;
                break;
            default:
                reportError(currentToken.getLexeme() + " is not an Actual Parameter.");
        }
    }

    private void expression() {
        //Expression              --> SimpleExpression OptionalRelationalPart
        //expression will leave a value on top of the stack

        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(70);
                simpleExpression();
                optionalRelationalPart();
                break;
            default:
                reportError(currentToken.getLexeme() + " is either NOT the correct type or NOT an allowed token!");
        }
    }

    private void optionalRelationalPart() {
        /*
        OptionalRelationalPart  --> RelationalOperator SimpleExpression
        --> ?
         */

        switch (currentToken.getType()) {
            case Token.MP_EQUAL:
            case Token.MP_GTHAN:
            case Token.MP_LTHAN:
            case Token.MP_GEQUAL:
            case Token.MP_LEQUAL:
            case Token.MP_NEQUAL:
                log(71);
                byte op = relationalOperator();
                simpleExpression();
                codeGen.compare(op);
                break;
            case Token.MP_COMMA:
            case Token.MP_SCOLON:
            case Token.MP_RPAREN:
            case Token.MP_DO:
            case Token.MP_DOWNTO:
            case Token.MP_ELSE:
            case Token.MP_END:
            case Token.MP_THEN:
            case Token.MP_TO:
            case Token.MP_UNTIL:
                //lambda
                log(72);
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot be a Relational Part.");
        }
    }

    private byte relationalOperator() {
        /*
        RelationalOperator --> "="
        --> "<"
        --> ">"
        --> "<="
        --> ">="
        --> "<>"
         */
        switch (currentToken.getType()) {
            case Token.MP_EQUAL:
                log(73);
                match(Token.MP_EQUAL);
                return Token.MP_EQUAL;
            case Token.MP_LTHAN:
                log(74);
                match(Token.MP_LTHAN);
                return Token.MP_LTHAN;
            case Token.MP_GTHAN:
                log(75);
                match(Token.MP_GTHAN);
                return Token.MP_GTHAN;
            case Token.MP_LEQUAL:
                log(76);
                match(Token.MP_LEQUAL);
                return Token.MP_LEQUAL;
            case Token.MP_GEQUAL:
                log(77);
                match(Token.MP_GEQUAL);
                return Token.MP_GEQUAL;
            case Token.MP_NEQUAL:
                log(78);
                match(Token.MP_NEQUAL);
                return Token.MP_NEQUAL;
            default:
                reportError(currentToken.getLexeme() + " is not a Relational Operator.");
                return -1;
        }
    }

    private void simpleExpression() {
        //SimpleExpression        --> OptionalSign Term TermTail

        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(79);
                byte type = optionalSign();
                term(type);
                termTail();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a Simple Expression.");
        }
    }

    private void termTail() {
        /*
        TermTail        --> AddingOperator Term TermTail
        --> ?
         */

        switch (currentToken.getType()) {
            case Token.MP_OR:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(80);
                byte operator = addingOperator();
                term((byte)-1);
                codeGen.addop(operator);
                termTail();
                break;
            case Token.MP_RPAREN:
            case Token.MP_COMMA:
            case Token.MP_DOWNTO:
            case Token.MP_TO:
            case Token.MP_DO:
            case Token.MP_THEN:
            case Token.MP_UNTIL:
            case Token.MP_ELSE:
            case Token.MP_END:
            case Token.MP_EQUAL:
            case Token.MP_GTHAN:
            case Token.MP_LTHAN:
            case Token.MP_GEQUAL:
            case Token.MP_LEQUAL:
            case Token.MP_NEQUAL:
            case Token.MP_SCOLON:
                //lambda
                log(81);
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot end a Term.");
        }
    }

    private byte optionalSign() {
        /*
        OptionalSign    --> "+"
        --> "-"
        --> ?
         */
        switch (currentToken.getType()) {
            case Token.MP_MINUS:
                log(83);
                match(Token.MP_MINUS);
                return Token.MP_MINUS;
            case Token.MP_PLUS:
                log(82);
                match(Token.MP_PLUS);
                return Token.MP_PLUS;
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
                //LAMBDA
                log(84);
                return -1;
            default:
                reportError("Optional Sign Syntax Error.");
                return -1;
        }
    }

    private byte addingOperator() {
        /*
        AddingOperator --> "+"
        --> "-"
        --> "or"
         */
        byte type;
        switch (currentToken.getType()) {
            case Token.MP_PLUS:
                log(85);
                type = currentToken.getType();
                match(Token.MP_PLUS);
                break;
            case Token.MP_MINUS:
                log(86);
                type = currentToken.getType();
                match(Token.MP_MINUS);
                break;
            case Token.MP_OR:
                log(87);
                type = currentToken.getType();
                match(Token.MP_OR);
                break;
            default:
                reportError("Illegal Adding Operator use.");
                type = -1;
        }
        return type;
    }

    private void term(byte sign) {
        //Term           --> Factor FactorTail

        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
                log(88);
                factor();
                //add optional sign if present right after push of factor
                codeGen.optionalSign(sign);
                factorTail();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a Term.");
        }
    }

    private void factorTail() {
        /*
        FactorTail     --> MultiplyingOperator Factor FactorTail
        --> ?
         */
        switch (currentToken.getType()) {
            case Token.MP_COMMA:
            case Token.MP_SCOLON:
            case Token.MP_RPAREN:
            case Token.MP_EQUAL:
            case Token.MP_GTHAN:
            case Token.MP_LTHAN:
            case Token.MP_GEQUAL:
            case Token.MP_LEQUAL:
            case Token.MP_NEQUAL:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
            case Token.MP_DO:
            case Token.MP_DOWNTO:
            case Token.MP_ELSE:
            case Token.MP_END:
            case Token.MP_OR:
            case Token.MP_THEN:
            case Token.MP_TO:
            case Token.MP_UNTIL:
                //lambda
                log(90);
                break;
            case Token.MP_AND:
            case Token.MP_TIMES:
            case Token.MP_DIV:
            case Token.MP_MOD:
                log(89);
                byte operator = multiplyingOperator();
                factor();
                //multiplyOp stack
                codeGen.multop(operator);
                factorTail();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot end a Factor.");
        }
    }

    private byte multiplyingOperator() {
        /* MultiplyingOperator     --> "*"
        --> "div"
        --> "mod"
        --> "and"
         */
        byte type;
        switch (currentToken.getType()) {
            case Token.MP_TIMES:
                log(91);
                type = Token.MP_TIMES;
                match(Token.MP_TIMES);
                break;
            case Token.MP_DIV:
                log(92);
                type = Token.MP_DIV;
                match(Token.MP_DIV);
                break;
            case Token.MP_MOD:
                log(93);
                type = Token.MP_MOD;
                match(Token.MP_MOD);
                break;
            case Token.MP_AND:
                log(94);
                type = Token.MP_AND;
                match(Token.MP_AND);
                break;
            default:
                type = -1;
                reportError(currentToken.getLexeme() + " is not a Multiplying Operator.");
        }
        return type;
    }

    private void factor() {
        /*
        Factor         --> UnsignedInteger
        --> VariableIdentifier
        --> "not" Factor
        --> "(" Expression ")"
        --> FunctionIdentifier OptionalActualParameterList
         */
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
                log(95);
                codeGen.pushValue(currentToken);
                match(Token.MP_INTEGER_LIT);
                break;
            case Token.MP_IDENTIFIER:
                try {
                    AbstractMethodSymbol symb = null;
                    if(methodName != null)
                        symb = (AbstractMethodSymbol) analyzer.lookupSymbol(methodName);
                    //if found symbol is a function, treat as a function
                    if (analyzer.lookupSymbol(currentToken.getLexeme()).getKind().equals("Function")) {
                        log(99);
                        if(symb != null)
                        {
                            if(symb.getParameters().get(parameterNumber).getMode() == SemanticRecord.MODE_REF)
                            {
                                //Throw error
                                ErrorHandler.logError("Cannot pass function by reference");
                            }
                        }


                        //match identifier to prevent issues
                        //functionIdentifier();
                        methodName = currentToken.getLexeme();
                        methodNameStack.push(methodName);
                        paramNumStack.push(parameterNumber);
                        match(Token.MP_IDENTIFIER);
                        codeGen.setup(methodName);
                        optionalActualParameterList();
                        parameterNumber = paramNumStack.pop();
                        methodName = methodNameStack.pop();
                        parameterNumber = 0;
                        codeGen.call(methodName);
                        codeGen.teardown(methodName);
                        methodName = null;
                        break;
                    }
                } catch (CompileException e) {
                    e.printMessage();
                }
                
                log(96);
                codeGen.pushVar(variableIdentifier().getLexeme(), methodName, parameterNumber);
                //variableIdentifier();
                break;
            case Token.MP_NOT:
                log(97);
                match(Token.MP_NOT);
                factor();
                codeGen.neg();
                break;
            case Token.MP_LPAREN:
                log(98);
                match(Token.MP_LPAREN);
                expression();//this will by default have a single resulting value on top of stack
                match(Token.MP_RPAREN);
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a Factor.");
        }
    }

    private Token identifier() {
        Token identifier;
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            identifier = currentToken;
            match(Token.MP_IDENTIFIER);
            return identifier;
        } else {
            reportError("Illegal Identifier");
            return null;
        }
    }

    private String programIdentifier() {
        //ProgramIdentifier    --> Identifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(100);
            return identifier().getLexeme();
        } else {
            reportError(currentToken.getLexeme() + " cannot be a Program Identifier.");
            return null;
        }
    }

    private Token variableIdentifier() {
        //VariableIdentifier   --> Identifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(101);
            //if symbol has already been created
            Token idTok = identifier();
            return idTok;
            // Cody - Comment out broken code (it's throwing an exception)
            /*
            if(analyzer.lookupSymbol(idTok.getLexeme()) == null)
            {
            reportError("Symbol not declared");
            }
             * */
        } else {
            reportError("Variable Identifier syntax error.");
        }
        return null;
    }

    private void procedureIdentifier(SemanticRecord record) {
        //ProcedureIdentifier  --> Identifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(102);
            record.setToken(identifier());
        } else {
            reportError(currentToken.getLexeme() + " cannot start a Procedure Identifier.");
        }
    }

    private void functionIdentifier(SemanticRecord record) {
        //FunctionIdentifier   --> Identifier
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(103);
            record.setToken(identifier());
        } else {
            reportError(currentToken.getLexeme() + " cannot start a Function Identifier.");

        }
    }

    private void booleanExpression() {
        //BooleanExpression    --> Expression
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(104);
                expression();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start a Boolean Expression.");
        }
    }

    private void ordinalExpression() {
        //OrdinalExpression    --> Expression
        switch (currentToken.getType()) {
            case Token.MP_INTEGER_LIT:
            case Token.MP_IDENTIFIER:
            case Token.MP_LPAREN:
            case Token.MP_NOT:
            case Token.MP_PLUS:
            case Token.MP_MINUS:
                log(105);
                expression();
                break;
            default:
                reportError(currentToken.getLexeme() + " cannot start an Ordinal Expression.");
        }
    }

    private ArrayList<Token> identifierList() {
        //IdentifierList       --> Identifier IdentifierTail
        ArrayList<Token> id_list = new ArrayList();
        if (currentToken.getType() == Token.MP_IDENTIFIER) {
            log(106);
            id_list.add(identifier());
            identifierTail(id_list);
            return id_list;
        } else {
            reportError("Identifier List syntax error.");
            return null;
        }
    }

    private void identifierTail(List<Token> id_list) {
        /*IdentifierTail       --> "," Identifier IdentifierTail
        --> ?    */
        if (currentToken.getType() == Token.MP_COMMA) {
            log(107);
            match(Token.MP_COMMA);
            id_list.add(identifier());
            identifierTail(id_list);
        } else if (currentToken.getType() == Token.MP_COLON) {
            //lambda
            log(108);
        } else {
            reportError("Identifier List Tail syntax error.");
        }
    }
}
